package com.stars.easyms.base.batch;

import lombok.Getter;
import lombok.NonNull;
import org.springframework.lang.Nullable;

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 批量提交时返回的结果
 *
 * @author guoguifang
 * @date 2018-10-19 10:07
 * @since 1.0.0
 */
public final class BatchResult<T> {

    private final List<T> successDatas = new ArrayList<>();

    private final Map<Integer, T> successOrdinalMap = new TreeMap<>();

    private final List<T> failDatas = new ArrayList<>();

    private final Map<Integer, T> failOrdinalMap = new TreeMap<>();

    private final List<Integer> successDataIndexs = new ArrayList<>();

    private final List<Integer> failDataIndexs = new ArrayList<>();

    private final List<Result<T>> resultList = new ArrayList<>();

    private final ReentrantLock successLock = new ReentrantLock();

    private final ReentrantLock failLock = new ReentrantLock();

    @NonNull
    public Map<Integer, T> getSuccessOrdinalMap() {
        return this.successOrdinalMap;
    }

    @NonNull
    public List<T> getSuccessDatas() {
        this.successLock.lock();
        try {
            if (this.successDatas.size() != this.successOrdinalMap.size()) {
                this.successDatas.clear();
                this.successDatas.addAll(this.successOrdinalMap.values());
            }
            return Collections.unmodifiableList(this.successDatas);
        } finally {
            this.successLock.unlock();
        }
    }

    @NonNull
    public List<Integer> getSuccessDataIndexs() {
        this.successLock.lock();
        try {
            if (this.successDataIndexs.size() != this.successOrdinalMap.size()) {
                this.successDataIndexs.clear();
                this.successDataIndexs.addAll(this.successOrdinalMap.keySet());
            }
            return Collections.unmodifiableList(this.successDataIndexs);
        } finally {
            this.successLock.unlock();
        }
    }

    public void addSuccessDatas(@Nullable Map<Integer, T> successDatas) {
        if (successDatas != null && successDatas.size() > 0) {
            this.successLock.lock();
            try {
                this.successOrdinalMap.putAll(successDatas);
            } finally {
                this.successLock.unlock();
            }
        }
    }

    public void clearSuccessDatas() {
        this.successDatas.clear();
    }

    @NonNull
    public Map<Integer, T> getFailOrdinalMap() {
        return this.failOrdinalMap;
    }

    @NonNull
    public List<T> getFailDatas() {
        this.failLock.lock();
        try {
            if (this.failDatas.size() != this.failOrdinalMap.size()) {
                this.failDatas.clear();
                this.failDatas.addAll(this.failOrdinalMap.values());
            }
            return Collections.unmodifiableList(this.failDatas);
        } finally {
            this.failLock.unlock();
        }
    }

    public List<Integer> getFailDataIndexs() {
        this.failLock.lock();
        try {
            if (this.failDataIndexs.size() != this.failOrdinalMap.size()) {
                this.failDataIndexs.clear();
                this.failDataIndexs.addAll(this.failOrdinalMap.keySet());
            }
            return Collections.unmodifiableList(this.failDataIndexs);
        } finally {
            this.failLock.unlock();
        }
    }

    public void addFailDatas(@Nullable Map<Integer, T> failDatas) {
        if (failDatas != null && failDatas.size() > 0) {
            this.failLock.lock();
            try {
                this.failOrdinalMap.putAll(failDatas);
            } finally {
                this.failLock.unlock();
            }
        }
    }

    public void clearFailDatas() {
        this.failDatas.clear();
    }

    @NonNull
    public List<Result<T>> getResultList() {
        this.successLock.lock();
        this.failLock.lock();
        try {
            if (this.resultList.size() != this.successOrdinalMap.size() + this.failOrdinalMap.size()) {
                Map<Integer, Result<T>> resultMap = new TreeMap<>();
                this.successOrdinalMap.forEach((index, value) -> resultMap.put(index, new Result<T>(index, value, true)));
                this.failOrdinalMap.forEach((index, value) -> resultMap.put(index, new Result<T>(index, value, false)));
                this.resultList.addAll(resultMap.values());
            }
            return Collections.unmodifiableList(this.resultList);
        } finally {
            this.failLock.unlock();
            this.successLock.unlock();
        }
    }

    public int getSuccessCount() {
        return this.successOrdinalMap.size();
    }

    public int getFailCount() {
        return this.failOrdinalMap.size();
    }

    @NonNull
    public static <T> BatchResult<T> ofSuccess(@Nullable List<T> list) {
        return of(list, true);
    }

    @NonNull
    public static <T> BatchResult<T> ofSuccessWithResult(@Nullable List<Result<T>> resultList) {
        return ofResult(resultList, true);
    }

    @NonNull
    public static <T> BatchResult<T> ofFail(@Nullable List<T> list) {
        return of(list, false);
    }

    @NonNull
    public static <T> BatchResult<T> ofFailWithResult(@Nullable List<Result<T>> resultList) {
        return ofResult(resultList, false);
    }

    private static <T> BatchResult<T> of(@Nullable List<T> list, boolean isSuccess) {
        BatchResult<T> batchResult = new BatchResult<>();
        if (list == null) {
            return batchResult;
        }
        Map<Integer, T> datas = new TreeMap<>();
        int index = 0;
        for (T t : list) {
            datas.put(index++, t);
        }
        if (isSuccess) {
            batchResult.addSuccessDatas(datas);
        } else {
            batchResult.addFailDatas(datas);
        }
        return batchResult;
    }

    @NonNull
    private static <T> BatchResult<T> ofResult(@Nullable List<Result<T>> resultList, boolean isSuccess) {
        BatchResult<T> batchResult = new BatchResult<>();
        if (resultList == null) {
            return batchResult;
        }
        Map<Integer, T> datas = new TreeMap<>();
        for (Result<T> result : resultList) {
            datas.put(result.getIndex(), result.getValue());
        }
        if (isSuccess) {
            batchResult.addSuccessDatas(datas);
        } else {
            batchResult.addFailDatas(datas);
        }
        return batchResult;
    }

    @Getter
    public static class Result<T> {

        private final int index;

        private final T value;

        private final boolean isSuccess;

        private Result(int index, T value, boolean isSuccess) {
            this.index = index;
            this.value = value;
            this.isSuccess = isSuccess;
        }

        public static <T> Result of(int index, T value, boolean isSuccess) {
            return new Result<>(index, value, isSuccess);
        }
    }
}